package com.ebs.md.fxmonitor.orders;

/**
 * Perforce Revision:
 *  $File: //depot/MarketData/fxmonitor/main/src/main/java/com/ebs/md/fxmonitor/orders/OBChangeListener.java $
 *  $Revision: #6 $  $Change: 116302 $  $DateTime: 2012/10/28 17:08:08 $  $Author: Arik.Sher $
 */

import java.io.Serializable;
import java.util.Calendar;
import java.util.List;

import javax.jms.JMSException;

import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import com.ebs.centralrepository.api.Floor;
import com.ebs.centralrepository.api.Translator;
import com.ebs.centralrepository.api.UnknownKeyException;
import com.ebs.commons.comp.IParsedPayload;
import com.ebs.commons.comp.IPubSubChannel;
import com.ebs.commons.logger.MessageText;
import com.ebs.marketdata.dictionary.PriceContext;
import com.ebs.md.api.BookSide;
import com.ebs.md.api.IDetailedBookEntry;
import com.ebs.md.api.IIdentifiedOrder;
import com.ebs.md.api.IOrderBook;
import com.ebs.md.api.IProduct;
import com.ebs.md.api.OrderBookListener;
import com.ebs.md.api.OrderOperation;
import com.ebs.md.api.Rate;
import com.ebs.md.api.orderbook.BookEntryPosition;
import com.ebs.md.api.orderbook.BookEntryPositionOfDeleted;
import com.ebs.md.api.product.PairProduct;
import com.ebs.md.fxmonitor.marketmonitor.model.OBCNew;
import com.ebs.md.fxmonitor.marketmonitor.model.OBCRemove;

/**
 * This OrderBookListener looks for OB changes. (new and deleted quotes)
 * 
 * it handle Multiple OrderBooks, by their Product(CurrencyPair).
 */
public class OBChangeListener
implements  OrderBookListener<IOrderBook<IDetailedBookEntry>, IDetailedBookEntry, IIdentifiedOrder>
{
    final protected Logger logger = Logger.getLogger(getClass());

	// Binfile Translator
	protected Translator translator;
	
	protected IPubSubChannel pubChannel;
	protected String         publishToTopicName;
	
	public void setPublishToTopicName(String publishToTopicName) {
		this.publishToTopicName = publishToTopicName;
	}
	public void setPubChannel(IPubSubChannel pubChannel) {
		this.pubChannel = pubChannel;
	}
	public void setTranslator(Translator translator) {
		this.translator = translator;
	}

	public void init() {
		Assert.notNull(pubChannel,         "'pubChannel' cannot be null");
		Assert.hasText(publishToTopicName, "'publishToTopicName' should contain text");
		Assert.notNull(translator,         "'translator' should not be null");
	}
	
	public void  afterNewBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry newBookEntry, IIdentifiedOrder order, BookEntryPosition bep) {
		final PairProduct pair = extractPair(orderbook);
		final String shortIsoName = pair.getShortIsoName();
		final PriceContext priceContext = translator.getPriceContext(shortIsoName);
		final Rate bestRate;
		final Rate entryRate = newBookEntry.getRate();
		if ( bep.isTOBchange() ) {
			// checking vs previous best.
			bestRate = getNextBest(orderbook, newBookEntry.getSide(), newBookEntry.getRate());
		} else {
			bestRate = getBest    (orderbook, newBookEntry.getSide());
		}
		
		int pipIncrementDiff = 0;
		
		if ( bestRate != null && entryRate != null  ) {
			if ( priceContext == null ) {
		    	logger.error(new MessageText("FXMON_0700004", shortIsoName) );
			} else {
				pipIncrementDiff = priceContext.pipsDiff(entryRate.getValue() , bestRate.getValue());
			}
		}
		
		String toppedQuoteId = null;
		if ( bep.isNewPosition() ) {
			// looking for topped quote id.
			Rate nextBest = orderbook.getNextBestRate(newBookEntry.getSide(), newBookEntry.getRate());
			
			if ( nextBest != null ) {
				List<IDetailedBookEntry> entriesAtRate;
				if ( BookSide.BID.equals(newBookEntry.getSide()) ) {
					entriesAtRate = orderbook.getTopBidsAtRate  (nextBest, 1);
				} else {
					entriesAtRate = orderbook.getTopOffersAtRate(nextBest, 1);
				}
				IDetailedBookEntry entryOfNextBest = entriesAtRate.get(0);
				
				toppedQuoteId = entryOfNextBest.getQuoteId();
			}
		}

		int firmId = lookupFirmId(newBookEntry);
		
		final OBCNew obcNew = new OBCNew(pair, newBookEntry, bep, firmId
				, pipIncrementDiff, toppedQuoteId);

		forwardDownstream(obcNew);
	}
	
	public void  afterRemoveBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry bookEntry, IIdentifiedOrder order, BookEntryPositionOfDeleted bepod) {
		final PairProduct pair = extractPair(orderbook);
		
		boolean isInterrupted = false;
		if ( OrderOperation.CANCEL.equals(order.getOperation()) ) {
			isInterrupted = true;
		}
		final Calendar entryCal      = bookEntry.getQuoteTime();
		final Calendar removeTimeCal = order.getTime();
		final long    entryLifeTime = removeTimeCal.getTimeInMillis() - entryCal.getTimeInMillis();
		int firmId = lookupFirmId(bookEntry);

		final OBCRemove obcRemove = new OBCRemove(pair, bookEntry, bepod, firmId
				, entryLifeTime, isInterrupted);
		
		forwardDownstream(obcRemove);
	}

	protected void forwardDownstream(Serializable obj) {
		try {
			pubChannel.submitMessage(publishToTopicName, obj);
		} catch (JMSException jmse) {
	    	logger.error(new MessageText("FXMON_0700002", "reasonable", jmse));
		} catch (Throwable t) {
			logger.error(new MessageText("FXMON_0700002", "unexpected", t));
		}
	}

	public void beforeModifyBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry bookEntry, IIdentifiedOrder order) {
		// do nothing
	}
	public void  afterModifyBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry bookEntry, IIdentifiedOrder order) {
		// do nothing
	}
	public void beforeNewBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry newBookEntry, IIdentifiedOrder order) {
		// do nothing
	}
	public void beforeRemoveBookEntry(IOrderBook<IDetailedBookEntry> orderbook, IDetailedBookEntry bookEntry, IIdentifiedOrder order) {
		// do nothing
	}
	public void beforeSnapshot(IOrderBook<IDetailedBookEntry> orderbook, Calendar snapshotTime, boolean isFirst) {
		// do nothing
	}
	public void  afterSnapshot(IOrderBook<IDetailedBookEntry> orderbook, Calendar snapshotTime, boolean isFirst) {
		// do nothing
	}

	protected Rate getBest(IOrderBook<IDetailedBookEntry> orderbook, BookSide side) {
		if ( BookSide.BID.equals(side) ) {
			return orderbook.getBestBid();
		} else {
			return orderbook.getBestOffer();
		}
	}
	
	protected Rate getNextBest(IOrderBook<IDetailedBookEntry> orderbook, BookSide side, Rate rate) {
		if ( BookSide.BID.equals(side) ) {
			return orderbook.getNextBestBid(rate);
		} else {
			return orderbook.getNextBestOffer(rate);
		}
	}
	
	protected void notifyOBChange(IParsedPayload payload) {
		try {
			pubChannel.submitMessage(publishToTopicName, payload);
		} catch (JMSException e) {
			//TODO: log it...
			throw new RuntimeException("unexpected when trying to publish OB change", e);
		}
	}


	protected int lookupFirmId(IDetailedBookEntry be) {
		int retVal = 0; // 0 means no firmId
		final Floor floor = lookupFloor(be);
		
		if ( floor != null ) {
			retVal = floor.getVirtualFloorId();
		}
		return retVal;
	}
		

	protected Floor lookupFloor(IDetailedBookEntry be) {
		final String dealCode = be.getDealCode();
		
		Floor floor = null;
		try {
			floor = translator.getFloor(dealCode);
		} catch (UnknownKeyException e) {
	    	logger.warn(new MessageText("FXMON_0700005", dealCode));
		}
		
		return floor;
	}
	
	protected PairProduct extractPair(IOrderBook<IDetailedBookEntry> orderbook) {
		//TODO: think of way without casting(maybe add generic to OrderBook)
		IProduct iproduct = orderbook.getProduct();
		return (PairProduct)iproduct;
	}
	
	
}
